#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<unistd.h>
#include<sys/types.h>    //waitpid 头文件
#include<sys/wait.h>
#include<ctype.h>        //这个是判断是空格的isspace头文件

//#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>        //这三个是O_RDONLY   的头文件


#define COMMAND_SIZE 1024

#define FORMAT "[%s@%s %s]# "

//这是shell定义的全局数据
#define MAXARGC 128
char *g_argv[MAXARGC];
int g_argc=0;


//环境变量表
#define MAX_ENVS 100
char *g_env[MAX_ENVS];
int g_envs = 0;


//别名映射表
//std::unordered_map<std::string,std::string> alias_list;


//对于重定向  关心的内容
#define NONE_REDIR 0       //没有重定向
#define INPUT_REDIR 1      //输入重定向
#define OUTPUT_REDIR 2     //输出重定向
#define APPEND_REDIR 3     //追加重定向

int redir = NONE_REDIR;    //默认的redir是没有重定向的
std::string filename;      //重定向的时候的文件名是谁




//用来做测试的
char cwd[1024];
char cwdenv[1024];

//退出码
int lastcode = 0;


//获取用户名
const char *GetUserName()

{
    const char* name=getenv("USER");
    return name ==NULL?"None":name;
}

//获取主机名
const char *GetHostName()
{
     const char *hostname = getenv("HOSTNAME");
     return hostname == NULL?"None":hostname;
}

//获取当前路径
const char *GetPwd()
{
//     const char *pwd = getenv("PWD");
 	const char *pwd = getcwd(cwd,sizeof(cwd));
 	
	if(pwd != NULL)
	{
	    snprintf(cwdenv,sizeof(cwdenv)+5,"PWD=%s",cwd);   //这里+5是防止发生截断
	    putenv(cwdenv);   //导环境变量
	}
	return pwd == NULL? "None" : pwd;
}

//获取家目录
const char *GetHome()
{
     const char *home = getenv("HOME");
     return home ==NULL?"":home;
}


//初始化  环境变量表
void InitEnv()
{
    extern char **environ;
    memset(g_env,0,sizeof(g_env));
    g_envs = 0;

   //本是从配制文件来   现在是从父shell来
  //这是获取环境变量
  for(int i=0;environ[i];i++)
   {
   	//首先  申请空间
	g_env[i] = (char*)malloc(strlen(environ[i])+1);
   	
	//拷贝  把environ[i]表中的信息拷贝到  g_env[i]中
	strcpy(g_env[i],environ[i]);
	g_envs++;
   }
   g_env[g_envs++] =(char*) "HAHA=for_test";    //做测试
   g_env[g_envs] = NULL;
	
    //导入环境变量
    for(int i=0;g_env[i];i++)
    {
	putenv(g_env[i]);
    }
}



//command
bool Cd()
{
	//cd  argc = 1
    if(g_argc==1)
    {
       std::string home = GetHome();
       if(home.empty())  return true;
       chdir(home.c_str());
     }
    else
    {
  	std::string where = g_argv[1];
  	//cd - //// cd~
  	if(where == "-")
  	{
  
  	}
  	else if(where == "~")
        {
  
  	}
	else
  	{
	    chdir(where.c_str());
        }
     }
	return true;
}


bool Echo()
{

	   if(g_argc == 2)
	   {
	      //1.echo  字符串
	      //2.echo  $？
	      //3.echo  $PATH     环境变量
	      std::string opt = g_argv[1];
	      if(opt == "$?")
	      {
		std::cout << lastcode <<std::endl;
		lastcode = 0;
		return true;
	      }
	      else if(opt[0] == '$')
	      {
		std::string env_name = opt.substr(1);
		const char* env_value = getenv(env_name.c_str());
	   	if(env_value)
		   std::cout<<env_value<<std::endl;
	      }  
	      else
	      {
		std::cout<<opt<<std::endl;
	      }
	   }
	return true;
}



//目录名
std::string DirName(const char *pwd)
{
//定义斜杠
#define SLASH "/"
	std::string dir = pwd;
	if(dir==SLASH) return SLASH;
	auto pos = dir.rfind(SLASH); //反向找
	if(pos == std::string::npos) return "BUG!!!";

	return dir.substr(pos+1);
}


void MakeCommandLine(char cmd_prompt[],int size)
{
     snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),DirName(GetPwd()).c_str());
//   snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),GetPwd());	
}

void PrintCommandPrompt()
{
     char prompt[COMMAND_SIZE];
     MakeCommandLine(prompt,sizeof(prompt));
     printf("%s",prompt);
     fflush(stdout);
}


bool GetCommandLine(char *out,int size)
{

  //当输入 ls -a -l ---->  实际是：ls -a -l\n   字符串带\n
    char *c=fgets(out,size,stdin);
    if(c ==NULL)
    {
	return false;
    }
    out[strlen(out)-1]=0;    //清理最后的\n
	
    if(strlen(out)==0)  return false;
    return true;
}

//命令行分析  "ls -a -l" --> "ls" "-a" "-l"
bool CommandParse(char *commandline)
{
#define SEP " "      //这是定义空格
	g_argc = 0;
	g_argv[g_argc++] = strtok(commandline,SEP);

	while((bool)(g_argv[g_argc++]=strtok(nullptr,SEP)));
	
	g_argc--; //永远都是后置++   会把NULL也统计进去   所以要减回去  
	
	return g_argc > 0?true:false;
}

//检测
void PrintArgv()
{
    for(int i=0;g_argv[i];i++)
    {
	printf("argv[%d]->%s\n",i,g_argv[i]);
    }
    printf("argc:%d\n",g_argc);
}

//检查并处理内建命令
bool CheckAndExecBuiltin()
{
	std::string cmd=g_argv[0];
	if(cmd == "cd")
	{
	    Cd();
	    return true;
	}
	else if(cmd == "echo")
	{
	   Echo();
	   return true;    //这个是内建命令
	}
	return false;
}

int Execute()
{
//---------------------  需要在执行命令中进行重定向  -----------------------------------
//注意！！！ 重定向不能说让父进程进行重定向（会影响覆盖式的打开方式）    只能让子进程进行重定向
	pid_t id = fork();
	if(id==0)
	{
	int fd = -1;
	   //子进程
	//让子进程检查重定向情况
	if(redir == INPUT_REDIR)    //输入重定向(文件一定存在)
	{
	    fd = open(filename.c_str(),O_RDONLY);
	    if(fd<0)   exit(1);    //fd小于0  直接退出   退出码设为1
	    dup2(fd,0);
	    close(fd);
	}
	else if(redir == OUTPUT_REDIR)    //输出重定向
	{
	    fd = open(filename.c_str(),O_CREAT | O_WRONLY | O_TRUNC,0666);   //打开文件 分别创建  写入  清空
	    if(fd<0)  exit(2);
	    dup2(fd,1);
	    close(fd);
	}
	else if(redir == APPEND_REDIR)    //追加重定向
	{
	    fd = open(filename.c_str(),O_CREAT | O_WRONLY | O_APPEND,0666);
	    if(fd<0)  exit(2);
	    dup2(fd,1);
	    close(fd);
	}
	else     //没有重定向   什么都不做
	{}


	   //做程序替换
	   execvp(g_argv[0],g_argv);
	   exit(1);
	}

	int status = 0;
	//父进程
	pid_t rid = waitpid(id,&status,0);
	if(rid>0)
	{
	   lastcode = WEXITSTATUS(status);
	}

	return 0;
}


//这个是去掉空格的接口
void TrimSpace(char commandline[],int &end)
{
	while(isspace(commandline[end]))
	{
	   end++;
	}
}





void RedirCheck(char commandline[])
{
//这两步操作做一下封装
	redir = NONE_REDIR;    //每次开始都重新更新为初始状态
	filename.clear();      //每次都是全新的  每次清空

	//遍历字符串
	int start = 0;
	int end = strlen(commandline)-1;

	//"ls -a -l > file.txt"
	while(end>start)   //倒着进行查找
	{
	   if(commandline[end] == '<')
	   {
		commandline[end++] = 0;   //end 最初指向小于符号，为0位置，先++一下就会符号的下一个位置   是f就不做处理
		TrimSpace(commandline,end);
		redir = INPUT_REDIR;   //对应输入重定向
		filename = commandline + end;
		break;
	   }
		else if(commandline[end] == '>')
		{
		    if(commandline[end-1] == '>')
		    {
			// >>  追加重定向
			commandline[end-1] = 0;	
			redir = APPEND_REDIR;
		    }
		    else
		    {
			// >   输出重定向
			redir = OUTPUT_REDIR;
		    }
		    //这三行都要被执行  放在下面
			commandline[end++]=0;
			TrimSpace(commandline,end);
		    	filename = commandline + end;   //这就指向了文件的开始
			break;
		}
	   else
	   {
		end--;
	   }
	}
}






int main()
{
    	//注：shell 在启动的时候，从系统中获取环境变量
	//我们的环境变量信息应该从父shell统一
	InitEnv();	
    while(1)
    {

        //1.输出命令行提示符
        PrintCommandPrompt();
    
        //2.获取用户输入的命令
        char commandline[COMMAND_SIZE];
        if(!GetCommandLine(commandline,sizeof(commandline)))
           continue;      //这个是命令行获取失败  重新获取  实现多组输入


	//3.重定向分析  “ls -a -l > file.txt” -> "ls -a -l" "file.txt" ---> 判定重定向方式（> / >>）
	RedirCheck(commandline);   //左边是要执行的命令 右边是要打开的文件  检查有对应的重定向，要把输入的commandline传进去
	//测试重定向
//	printf("redir：%d，filename：%s\n",redir,filename.c_str());
	

	//4.把输入的字符串给拆除 命令行分析  "ls -a -l" ---> “ls” "-a" "-l"
	if(!CommandParse(commandline))
		continue;
	//PrintArgv();

	//5.检测并处理内建命令
	if(CheckAndExecBuiltin())
		continue;

	//6.执行命令
	Execute();   //要在执行命令内部进行重定向处理
    }
        return 0;
}
